﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Windows.Input;
using ScrumFactory.Composition.ViewModel;
using ScrumFactory.Composition;
using ScrumFactory.Services;


namespace ScrumFactory.IterationPlanning.ViewModel {

    /// <summary>
    /// Iteration Planning View Model.
    /// </summary>
    [Export(typeof(IIterationPlanningViewModel))]
    [Export(typeof(IPanelViewModel))]
    public class IterationPlanningViewModel : BasePanelViewModel, IIterationPlanningViewModel, IPanelViewModel, INotifyPropertyChanged {

        private IBacklogService backlogService;
        private IProjectsService projectsService;
        private IBackgroundExecutor executor;
        private IEventAggregator aggregator;

        /// <summary>
        /// Initializes a new instance of the <see cref="IterationPlanningViewModel"/> class.
        /// </summary>
        /// <param name="backlogService">The backlog service.</param>
        /// <param name="projectsService">The iteration service.</param>
        /// <param name="backgroundExecutor">The background executor.</param>
        /// <param name="eventAggregator">The event aggregator.</param>
        [ImportingConstructor()]
        public IterationPlanningViewModel(
            [Import] IBacklogService backlogService,
            [Import] IProjectsService projectsService,
            [Import] IBackgroundExecutor backgroundExecutor,
            [Import] IEventAggregator eventAggregator) {

            this.projectsService = projectsService;
            this.backlogService = backlogService;
            this.executor = backgroundExecutor;
            this.aggregator = eventAggregator;

            // subscribe events
            aggregator.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, OnViewProjectDetails);            
            aggregator.Subscribe<ICollection<BacklogItem>>(ScrumFactoryEvent.BacklogItemsLoaded, b => { UpdateSprintPlans(); });

            aggregator.Subscribe<object[]>(ScrumFactoryEvent.BacklogItemMoved,
                p => { RefreshMovedItemSprints((int)p[1], (int)p[2]); }
            );

            aggregator.Subscribe<ICollection<Sprint>>(ScrumFactoryEvent.SprintsDateChanged, OnSprintsDateChanged);

            // assign commands
            CreateNewSprintCommand = new DelegateCommand(CreateNewSprint);


        }

        private void OnSprintsDateChanged(ICollection<Sprint> sprints) {
            // need to fix the references, i did not figure out how to do this in a better way
            foreach (Sprint s in sprints)
                s.Project = Project;

            Project.Sprints = new List<Sprint>(sprints);
            CreateSprintsViewModels();
        }

        private void OnViewProjectDetails(Project project) {
            Project = project;
            CreateSprintsViewModels();         
        }

        protected void CreateSprintsViewModels() {
            // sets project sprints
            if (Project.Sprints == null)
                Project.Sprints = new List<Sprint>();

            // can not live without sprints
            if (Project.Sprints == null || Project.Sprints.Count == 0) {
                SprintPlans = new ObservableCollection<ISprintViewModel>();
                CreateNewSprint();
            }

            // update the sprint plans
            UpdateSprintPlans();
        }

        /// <summary>
        /// Creates the new sprint and saves it.
        /// </summary>
        private void CreateNewSprint() {

            if (Project.Sprints == null)
                Project.Sprints = new List<Sprint>();

            DateTime startDate = DateTime.Today;
            if (Project.Sprints.Count > 0)
                startDate = Project.Sprints.Last().EndDate.AddDays(1);

            DateTime endDate = startDate.AddDays(15);

            Sprint newSprint = new Sprint() {
                SprintUId = Guid.NewGuid().ToString(),
                SprintNumber = Project.NextSprintNumber,
                StartDate = startDate,
                EndDate = endDate,
                ProjectUId = Project.ProjectUId,                                
                Project = Project
            };
            Project.Sprints.Add(newSprint);

            SprintPlans.Add(new ViewModel.SprintViewModel(
                projectsService,
                executor,
                aggregator,
                newSprint,
                Items));

            // update the old last sprint so the move next command get enabled
            if (BacklogViewModel.Items != null) {
                int oldLastSprintNumber = SprintPlans.Count - 1;
                foreach (IBacklogItemDetailViewModel ivm in Items.Where(vm => vm.Item.SprintNumber.Equals(oldLastSprintNumber)))
                    ivm.RefreshUI();
            }

            executor.StartBackgroundTask(() => projectsService.AddSprint(Project.ProjectUId, newSprint), () => { });            
        }

        /// <summary>
        /// Finds the sprint plan by a given sprint number.
        /// </summary>
        /// <param name="sprintNumber">The sprint number.</param>
        /// <returns></returns>
        private ISprintViewModel FindSprintPlanByNumber(int? sprintNumber) {
            if (sprintNumber == null)
                return null;
            return SprintPlans.SingleOrDefault(sp => sp.Sprint.SprintNumber.Equals(sprintNumber));
        }

        /// <summary>
        /// Refreshes the moved item sprints.
        /// It means, refreshes the sprint plan from where the item has been moved and the
        /// sprint plan to where it has been moved.
        /// </summary>
        /// <param name="sourceSprintNumber">The source sprint number.</param>
        /// <param name="targetSprintNumber">The target sprint number.</param>
        private void RefreshMovedItemSprints(int sourceSprintNumber, int targetSprintNumber) {

            ISprintViewModel sprintSource = FindSprintPlanByNumber(sourceSprintNumber);
            if(sprintSource!=null)
                sprintSource.RefreshUI();

            ISprintViewModel sprintTarget = FindSprintPlanByNumber(targetSprintNumber);
            if(sprintTarget!=null)
                sprintTarget.RefreshUI();
           
        }                

        /// <summary>
        /// Updates the Sprint plans.
        /// </summary>
        private void UpdateSprintPlans() {
                        
            if(Project==null || Project.Sprints==null || Project.BacklogItems==null)
                return;
            
            // create a new sprintplan collection
            SprintPlans = new ObservableCollection<ISprintViewModel>();
         
            foreach (Sprint s in Project.Sprints.Where(s => s.SprintNumber >= Project.CurrentValidSprint.SprintNumber).OrderBy(s => s.SprintNumber)) {
                ViewModel.SprintViewModel sp = new ViewModel.SprintViewModel(projectsService, executor, aggregator, s, Items);                 
                SprintPlans.Add(sp);                
            }

            OnPropertyChanged("SprintPlans");

            NotFinishedItems = Items.Where(
                vm => vm.Item.Status != (short)BacklogItemStatus.ITEM_DONE
                    && vm.Item.Status != (short)BacklogItemStatus.ITEM_CANCELED
                    && vm.Item.SprintNumber < Project.CurrentValidSprint.SprintNumber).ToList();

            OnPropertyChanged("NotFinishedItems");            
        }


        /// <summary>
        /// Gets the backlog view model.
        /// </summary>
        /// <value>The backlog view model.</value>
        [Import(typeof(IBacklogViewModel))]
        private IBacklogViewModel BacklogViewModel { get; set; }
        
        #region IIterationPlanningViewModel Members

        /// <summary>
        /// Gets the sprint plans.
        /// </summary>
        /// <value>The sprint plans.</value>
        public ObservableCollection<ISprintViewModel> SprintPlans { private set; get; }

        /// <summary>
        /// Gets the project.
        /// </summary>
        /// <value>The project.</value>
        public Project Project { private set; get; }

        /// <summary>
        /// Gets or sets the view.
        /// </summary>
        /// <value>The view.</value>
        [Import(typeof(IIterationPlanningView))]
        public IIterationPlanningView View { get; set; }

        /// <summary>
        /// Gets the not finished items.
        /// </summary>
        /// <value>The not finished items.</value>
        public ICollection<IBacklogItemDetailViewModel> NotFinishedItems { get; private set; }

        /// <summary>
        /// Gets the backlog items.
        /// </summary>
        /// <value>The items.</value>
        public ICollection<IBacklogItemDetailViewModel> Items {
            get {
                return BacklogViewModel.Items;
            }
        }

        /// <summary>
        /// Gets the create new sprint command.
        /// </summary>
        /// <value>The create new sprint command.</value>
        public ICommand CreateNewSprintCommand { private set; get; }


        //public ICommand RequestDataFromServiceCommand { get; private set; }

        #endregion

        #region IPanelViewModel Members

        /// <summary>
        /// Gets the name of the panel.
        /// </summary>
        /// <value>The name of the panel.</value>
        public string PanelName {
            get {
                return Properties.Resources.Sprints;
            }
        }

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        object IPanelViewModel.View {
            get {
                return this.View;
            }
        }

        /// <summary>
        /// Gets the panel display order when it is displayed at the view.
        /// </summary>
        /// <value>The panel display order.</value>
        public int PanelDisplayOrder {
            get {
                return 400;
            }
        }

        /// <summary>
        /// Gets the panel Unique id.
        /// </summary>
        /// <value>The panel Unique id.</value>
        public string PanelUId {
            get {
                return "SPRINT_TAB";
            }
        }

        /// <summary>
        /// Gets the panel placement.
        /// </summary>
        /// <value>The panel placement.</value>
        public PanelPlacements PanelPlacement {
            get { return PanelPlacements.ProjectTab; }
        }

        #endregion


    }
}
